package com.tbit.main.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tbit.Version;
import com.tbit.common.utils.BeanUtils;
import com.tbit.main.constant.DaoParamConstant;
import com.tbit.main.constant.ReturnPosLogConstant;
import com.tbit.main.constant.SysConstant;
import com.tbit.main.constant.UqbikeManagerConfig;
import com.tbit.main.controller.interceptor.LoginUserContextHolder;
import com.tbit.main.dao.core.RideLogDao;
import com.tbit.main.exception.BaseException;
import com.tbit.main.h2.pojo.ParkPoint;
import com.tbit.main.pojo.*;
import com.tbit.main.pojo.VO.EditOrderReqVO;
import com.tbit.main.pojo.VO.RideLogReqVO;
import com.tbit.main.pojo.VO.RideLogRespVO;
import com.tbit.main.remote.rpc.UQHttpManager;
import com.tbit.main.service.*;
import com.tbit.main.util.LogUtil;
import com.tbit.main.util.MD5PasswordUtil;
import com.tbit.main.util.PageUtils;
import com.tbit.main.util.StringUtil;
import com.tbit.manager.api.UserCardApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service("rideLogService")
public class RideLogServiceImpl implements RideLogService {

    @Resource
    private RideLogDao rideLogDao;

    @Resource
    private AccountService accountService;

    @Resource
    private MachineService machineService;

    @Resource
    private UserService userService;

    @Resource
    private ParkPointService parkPointService;

    @Resource
    private BorrowPosLogService borrowPosLogService;

    @Resource
    private ReturnPosLogService returnPosLogService;

    @Resource
    private RefundOrderService refundOrderService;

    @Resource
    private RefundLogService refundLogService;

    @Autowired
    private UserCardApi userCardApi;

    /**
     * 金额格式化
     */
    private final DecimalFormat df = new DecimalFormat("0.00");

    @Override
    public RideLog findLastByMachineId(Integer machineId) {
        return rideLogDao.findLastByMachineId(machineId);
    }

    @Override
    public RideSta staByTime(Integer accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, accountId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.staByTime(params);
    }

    @Override
    public RideSta staRegionIdByTime(Integer regionId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put("regionId", regionId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.staRegionIdByTime(params);
    }

    @Override
    public RideSta staByTimeByAccountIds(List<Integer> accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, StringUtil.getListString(accountId));
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.staByTimeByAccountIds(params);
    }

    @Override
    public List<RideSta> staByTimeByAccountIdsGroup(List<Integer> accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, StringUtil.getListString(accountId));
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.staByTimeByAccountIdsGroup(params);
    }

    @Override
    public RideSta staByAccountId(Integer accountId) {
        return rideLogDao.staByAccountId(accountId);
    }

    @Override
    public RideSta staNotPayByTime(Integer accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, accountId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);

        return rideLogDao.staNotPayByTime(params);
    }

    @Override
    public RideSta staAfterPayByTime(Integer accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, accountId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);

        return rideLogDao.staAfterPayByTime(params);
    }

    @Override
    public RideLog getByOrderNO(String orderNO) {
        return rideLogDao.getByOrderNO(orderNO);
    }

    @Override
    public List<RideLog> getByOrderNOs(List<String> orderNOs) {
        Map<String, Object> map = new HashMap<>();
        map.put("orderNOs", StringUtil.getStringListString(orderNOs));
        return rideLogDao.getByOrderNOs(map);
    }

    @Override
    public void addDispatchLog(OrderDispatchLog orderDispatchLog) {
        rideLogDao.addDispatchLog(orderDispatchLog);
    }

    @Override
    public Integer statByParkCount(Integer accountId, Integer type, String startTime, String endTime) {
        Map<String, Object> map = new HashMap<>();
        map.put(DaoParamConstant.accountId, accountId);
        map.put(DaoParamConstant.startTime, startTime);
        map.put(DaoParamConstant.endTime, endTime);
        if (type == 0) {
            return rideLogDao.statByStartParkCount(map);
        } else {
            return rideLogDao.statByEndParkCount(map);
        }
    }

    @Override
    public List<StatPark> statByPark(Integer accountId, Integer type, String startTime, String endTime, Integer pageNO, Integer rowCount) {
        Map<String, Object> map = new HashMap<>();
        map.put(DaoParamConstant.accountId, accountId);
        map.put(DaoParamConstant.startTime, startTime);
        map.put(DaoParamConstant.endTime, endTime);
        map.put("start", (pageNO - 1) * rowCount);
        map.put("rowCount", rowCount);
        if (type == 0) {
            return rideLogDao.statByStartPark(map);
        } else {
            return rideLogDao.statByEndPark(map);
        }
    }

    @Override
    public RideSta getByAccountId(Integer accountId) {
        return rideLogDao.getByAccountId(accountId);
    }

    @Override
    public RideSta getByAccountIds(List<Integer> accountId) {
        Map<String, Object> map = new HashMap<>();
        map.put(DaoParamConstant.accountId, StringUtil.getListString(accountId));
        return rideLogDao.getByAccountIds(map);
    }

    @Override
    public List<RideLog> getByMachineId(Integer machineId, Integer userId, String startTime, String endTime) {

        Map<String, Object> map = new HashMap<>();
        map.put("machineId", machineId);
        map.put("userId", userId);
        map.put(DaoParamConstant.startTime, startTime);
        map.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.getByMachineId(map);

    }

    @Override
    public List<RideLog> getBorrow() {
        return rideLogDao.getBorrow();
    }

    @Override
    public Integer newMile(Integer accountId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountId, accountId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.newMileage(params);
    }

    @Override
    public Integer newDayMile(List<Integer> accountIds, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put(DaoParamConstant.accountIds, StringUtil.getListString(accountIds));
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.newDayMile(params);
    }

    @Override
    public List<RideLog> getMachineIdStartTime(Integer machineId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put("machineId", machineId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.getMachineIdStartTime(params);
    }

    @Override
    public List<Integer> getMachineIdTime(Integer machineId, String startTime, String endTime) {
        Map<String, Object> params = new HashMap<>();
        params.put("machineId", machineId);
        params.put(DaoParamConstant.startTime, startTime);
        params.put(DaoParamConstant.endTime, endTime);
        return rideLogDao.getMachineIdTime(params);
    }

    @Override
    public List<AccountDayStat> statYesterdayOrderGroupBy(String startTime, String endTime) {
        return null;
    }

    /**
     * 客户端结束订单
     */
    @Override
    public Integer finishOrder(AccountUser accountUser, FinishOrderDTO finishOrderDTO, Integer accountId, Integer rideCardId, Integer vipId, Integer couponUserId) {
        if (finishOrderDTO.getMoney() < 0) {
            throw new BaseException("订单金额有误!");
        }
        //查询用户最新一条订单
        RideLog rideLog = rideLogDao.queryLatestRideLog(finishOrderDTO.getUserId());
        if (rideLog == null) {
            throw new BaseException("订单不存在!");
        }
        if (rideLog.getEndTime() != null) {
            throw new BaseException("订单已结束,请勿重复结束订单!");
        }

        /*使用卡券抵扣*/
        if (Objects.nonNull(rideCardId) || Objects.nonNull(vipId) || Objects.nonNull(couponUserId)) {
            Map<String, Object> map = userCardApi.consumptionCardCoupon(finishOrderDTO.getUserId(), accountId, rideCardId, vipId, couponUserId);
            finishOrderDTO.setRemark(finishOrderDTO.getRemark() + map.getOrDefault("remark", "").toString());
        }

        String url = UqbikeManagerConfig.clientsite + "/machine/finishOrder.do";
        String success = "1";
        Map<String, Object> map = initRequestMap(accountUser, finishOrderDTO);
        String body = null;
        try {
            LogUtil.info("map===" + map.toString());
            body = HttpRequest.get(url)
                    .header("U-APP-VERSION", Version.VERSION)
                    .form(map)
                    .execute()
                    .body();
            LogUtil.info("调用客户端接口结束订单,请求参数,客户端返回" + JSON.toJSONString(map) + body);
            JSONObject jsonObject = JSON.parseObject(body);
            if (!success.equals(jsonObject.get("ret").toString())) {
                throw new BaseException(jsonObject.get("msg").toString());
            } else {
                return jsonObject.getInteger("data");
            }
        } catch (Exception e) {
            LogUtil.error("调用客户端接口结束订单失败,请求参数,客户端返,exception" + JSON.toJSONString(map) + body, e);
            throw new BaseException(e.getMessage());
        }
    }

    @Override
    public Date getAfterLatest(String time, Integer machineId) {
        return rideLogDao.getAfterLatest(time, machineId);
    }

    @Override
    public PageResult<RideLogRespVO> getRideLogPage(RideLogReqVO reqVO) {

        buildParam(reqVO);

        IPage<RideLog> page = rideLogDao.selectRideLogPage(PageUtils.buildMybatisPage(reqVO), reqVO);

        IPage<RideLogRespVO> pageRespVO = toPageRespVO(reqVO.getMapType(), page);

        return PageResult.create(pageRespVO);
    }

    private IPage<RideLogRespVO> toPageRespVO(Integer mapType, IPage<RideLog> page) {
        List<RideLog> list = page.getRecords();

        Set<Integer> userIds = new HashSet<>();
        Set<Integer> pointIds = new HashSet<>();
        Set<Integer> accountIds = new HashSet<>();
        List<String> orderNos = new ArrayList<>();

        list.forEach(rideLog -> {
            userIds.add(rideLog.getUserId());
            pointIds.add(rideLog.getStartPointId());
            pointIds.add(rideLog.getEndPointId());
            orderNos.add(rideLog.getOrderNO());
            accountIds.add(rideLog.getAccountId());
        });

        Map<String, List<RefundOrder>> refundOrders = refundOrderService.getByBusinessNos(orderNos).stream().collect(Collectors.groupingBy(RefundOrder::getBusinessNO));

        Map<String, List<RefundLog>> refundLogs = refundLogService.getByBusinessNos(orderNos).stream().collect(Collectors.groupingBy(RefundLog::getBusinessNO));

        Map<Integer, User> users = userService.getUserIds(new ArrayList<>(userIds)).stream().collect(Collectors.toMap(User::getUserId, Function.identity()));
        Map<Integer, String> parkPoints = parkPointService.getByParkPoints(new ArrayList<>(pointIds)).stream().collect(Collectors.toMap(ParkPoint::getParkPointId, ParkPoint::getName));
        Map<Integer, String> accounts = accountService.getAccountIds(new ArrayList<>(accountIds)).stream().collect(Collectors.toMap(Account::getAccountId, Account::getName));

        Map<String, BorrowPosLog> borrowPosLogs = new HashMap<>();
        for (BorrowPosLog borrowPosLog : borrowPosLogService.getBorrowPosLog(mapType, orderNos)) {
            borrowPosLogs.put(getKey(borrowPosLog.getOrderNo(), borrowPosLog.getPosFrom()), borrowPosLog);
        }
        Map<String, ReturnPosLog> returnPosLogs = new HashMap<>();
        for (ReturnPosLog returnPosLog : returnPosLogService.getReturnPosLog(mapType, orderNos)) {
            returnPosLogs.put(getKey(returnPosLog.getOrderNO(), returnPosLog.getPosFrom()), returnPosLog);
        }

        return page.convert(rideLog -> {
            RideLogRespVO respVO = BeanUtil.toBean(rideLog, RideLogRespVO.class);
            User user = users.get(rideLog.getUserId());
            if (user != null) {
                respVO.setUsername(user.getName());
                respVO.setPhone(user.getPhone());
            }
            DateTimeFormatter normDatetimeFormatter = DatePattern.NORM_DATETIME_FORMATTER;
            respVO.setStartTime(LocalDateTimeUtil.toEpochMilli(LocalDateTime.parse(rideLog.getStartTime(), normDatetimeFormatter)));

            String endTime = rideLog.getEndTime();
            if (StrUtil.isNotBlank(endTime)) {
                respVO.setEndTime(LocalDateTimeUtil.toEpochMilli(LocalDateTime.parse(endTime, normDatetimeFormatter)));
            }

            String payTime = rideLog.getPayTime();
            if (StrUtil.isNotBlank(payTime)) {
                respVO.setPayTime(LocalDateTimeUtil.toEpochMilli(LocalDateTime.parse(payTime, normDatetimeFormatter)));
            }

            respVO.setStartPointName(parkPoints.get(rideLog.getStartPointId()));
            respVO.setEndPointIdName(parkPoints.get(rideLog.getEndPointId()));
            respVO.setAccountName(accounts.get(rideLog.getAccountId()));

            BorrowPosLog machineBorrowPosLog = borrowPosLogs.get(getKey(rideLog.getOrderNO(), ReturnPosLogConstant.TYPE_MACHINE));
            BorrowPosLog mobileBorrowPoslog = borrowPosLogs.get(getKey(rideLog.getOrderNO(), ReturnPosLogConstant.TYPE_MOBILE));
            respVO.setMachineBorrowPos(machineBorrowPosLog);
            respVO.setMobileBorrowPos(mobileBorrowPoslog);

            ReturnPosLog machineReturnPosLog = returnPosLogs.get(getKey(rideLog.getOrderNO(), ReturnPosLogConstant.TYPE_MACHINE));
            ReturnPosLog mobileReturnPoslog = returnPosLogs.get(getKey(rideLog.getOrderNO(), ReturnPosLogConstant.TYPE_MOBILE));
            respVO.setMachineReturnPos(machineReturnPosLog);
            respVO.setMobileReturnPos(mobileReturnPoslog);

            Integer refundStatus = refundOrders.getOrDefault(rideLog.getOrderNO(), Collections.emptyList())
                    .stream().filter(refundOrder -> Objects.equals(refundOrder.getRefundStatus(), 0))
                    .findAny().map(refundOrder -> 0).orElse(null);
            respVO.setRefundStatus(refundStatus);

            BigDecimal totalRefundMoney = refundLogs.getOrDefault(rideLog.getOrderNO(), Collections.emptyList())
                            .stream()
                            .map(refundLog -> BigDecimal.valueOf(refundLog.getChangeMoney()))
                            .reduce(BigDecimal.ZERO, BigDecimal::add)
                            .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
            respVO.setRefundMoney(totalRefundMoney.doubleValue());
            return respVO;
        });
    }

    private String getKey(String orderNo, Integer posFrom) {
        return orderNo + StrPool.DASHED + posFrom;
    }

    private void buildParam(RideLogReqVO reqVO) {
        AccountUser loginUser = LoginUserContextHolder.getLoginUser();

        // 未选区域查询登录用户下的所持有区域
        if (Objects.isNull(reqVO.getAccountId()) && CollUtil.isNotEmpty(loginUser.getAgents())) {
            String accountIds = loginUser.getAgents().stream().map(Agent::getAccountId).map(String::valueOf).collect(Collectors.joining(StrPool.COMMA));
            reqVO.setAccountIds(accountIds);
        }
        String phoneOrOrderNoOrUserCode = reqVO.getPhoneOrOrderNoOrUserCode();

        // 1. 手机号获取用户
        User user = userService.getByPhone(loginUser.getBrandId(), phoneOrOrderNoOrUserCode);
        if (user != null) {
            reqVO.setUserId(user.getUserId());
            return;
        }

        // 2. 订单号获取订单
        RideLog rideLog = rideLogDao.getByOrderNO(phoneOrOrderNoOrUserCode);
        List<Integer> accountIdList = loginUser.getAgents().stream().map(Agent::getAccountId).collect(Collectors.toList());
        if (rideLog != null && accountIdList.contains(rideLog.getAccountId())) {
            reqVO.setOrderNo(rideLog.getOrderNO());
            return;
        }

        // 3. 以上都不是就认为是车辆编号
        reqVO.setUserCode(phoneOrOrderNoOrUserCode);
        // 车辆编号无索引，查询出machineId
        Machine machine = machineService.getByUseCode(phoneOrOrderNoOrUserCode);
        if (machine == null) {
            throw new BaseException("手机号/车辆编号/订单号不存在");
        }
        reqVO.setMachineId(machine.getMachineId());
    }

    @Override
    public List<RideLog> getUserCode(String codePhone, String startTime, String endTime, AccountUser accountUser) {
        List<RideLog> rideLogList;

        Machine machine = machineService.getByUseCode(codePhone);
        if (machine != null) {
            /* 设备权限判断*/
            machineService.auth(accountUser.getAgents(), machine.getMachineId());
            rideLogList = this.getByMachineId(machine.getMachineId(), null, startTime, endTime);
        } else {
            Integer account = accountService.getByAccountAndType(accountUser.getAccountId(), 1).getAccountId();
            User user = userService.getByPhone(account, codePhone);
            if (user == null) {
                throw new BaseException("AccountUser.notExist");
            }
            rideLogList = this.getByMachineId(null, user.getUserId(), startTime, endTime);
        }

        for (RideLog rideLog : rideLogList) {
            rideLog.setMoney(NumberUtil.div(BigDecimal.valueOf(100), rideLog.getMoney(), 2).doubleValue());
        }

        return rideLogList;
    }

    /**
     * 初始化请求map
     */
    public Map<String, Object> initRequestMap(AccountUser accountUser, FinishOrderDTO finishOrderDTO) {
        long date = System.currentTimeMillis();
        String param = "youqu";
        Map<String, Object> map = new HashMap<>();
        map.put("userId", finishOrderDTO.getUserId());
        map.put("money", finishOrderDTO.getMoney());
        map.put("remark", finishOrderDTO.getRemark());
        //调度类型1站外禁停区，2超区，3头盔锁
        map.put("dispatchType", finishOrderDTO.getDispatchType());
        //类型 为2则是人工结束订单，3是超时结束订单
        map.put("type", finishOrderDTO.getType());
        map.put("sign", MD5PasswordUtil.MD5("TBIT" + date + param));
        map.put("date", date);
        map.put("param", param);
        map.put("operatorId", accountUser.getAccountUserId());
        map.put("operatorName", accountUser.getName());
        map.put("operatorPhone", accountUser.getPhone());
        return map;
    }

    @Override
    public List<RideLog> getRecentUser(Integer machineId, Integer regionId, Integer pageNO, Integer rowCount) {
        List<RideLog> rideLogList;
        /* 设备是否存在判断*/
        Machine machine = machineService.getByMachineId(machineId);
        if (machine == null) {
            throw new BaseException("Machine.notExist");
        }

        pageNO = (pageNO - 1) * rowCount;
        return rideLogDao.getRecentUserByUserCode(machineId,regionId,pageNO,rowCount);
    }

    @Override
    public void editOrder(EditOrderReqVO reqVO) {
        reqVO.setAccountUserId(LoginUserContextHolder.getLoginUserId());
        UQHttpManager.editOrder(BeanUtils.beanToMap(reqVO));
    }

    @Override
    public RideLog getRecentOrderByUserId(Integer userId) {
        return rideLogDao.getRecentOrderByUserId(userId);
    }

    @Override
    public PageResult<RideLogRespVO> getRideLogByUserId(Integer userId, Integer pageNO, Integer rowCount) {
        RideLogReqVO rideLogReqVO = new RideLogReqVO();
        rideLogReqVO.setUserId(userId);
        Page<RideLog> page = new Page<>(pageNO, rowCount);
        IPage<RideLog> RideLogPage = rideLogDao.selectRideLogPage(page, rideLogReqVO);
        IPage<RideLogRespVO> pageRespVOPage = toPageRespVO(SysConstant.GOOGLE, RideLogPage);
        for (RideLogRespVO record : pageRespVOPage.getRecords()) {
            Long endTime;
            if (record.getEndTime() == null) {
                endTime = DateUtil.current();
            }else {
                endTime = record.getEndTime();
            }

            long difference = endTime - record.getStartTime();

            // 将毫秒转换为秒
            long seconds = difference / 1000;

            // 计算小时、分钟和剩余的秒数
            long hours = seconds / 3600;
            seconds %= 3600;
            long minutes = seconds / 60;
            seconds %= 60;

            // 输出格式化的时间差异，使用String.format来确保两位数显示
            record.setRideTime(String.format("%02d时%02d分%02d秒", hours, minutes, seconds));
        }
        return PageResult.create(pageRespVOPage);
    }
}